﻿using System;
using System.Text.RegularExpressions;
using System.Collections.Generic;
using System.Text;

namespace CampaignDataPdfConverter
{
    public class CampaignReader
    {
        // how the actual reading is done
        public void ConsumeLine(string line, int lineNumber)
        {
            // rule #1: empty line means nothing -- return to the file and get another line
            if (String.IsNullOrEmpty(line))
                return;

            // rule #2: must the state be switched?
            line = line.TrimEnd(); // get rid of possible special characters at the end of the line
            {
                string trimmedLine = line.Trim();
                if (line.Contains("Brand:"))
                {
                    // prepare to read ad group definition
                    currentState_ = ReaderState.ReadingAdGroupDefinition;
                    currentBrand_ = null;
                    currentCampaign_ = null;
                    currentAdGroup_ = null;
                    currentAd_ = null;
                }
                else if (trimmedLine.StartsWith("Traditional Format") && currentState_ != ReaderState.SkippingSitelinks)
                {
                    // prepare to read ad definition
                    currentState_ = ReaderState.ReadingAdDefinition;
                    currentAd_ = null;
                    return; // skip the rest of the line and prepare to read starting at next line
                }
                else if (trimmedLine.StartsWith("Keyword Number"))
                {
                    // prepare to read keywords for current ad group
                    currentState_ = ReaderState.ReadingKeywords;
                    currentAd_ = null;
                    return; // skip the rest of the line and prepare to read starting at next line
                }
                else if (trimmedLine.StartsWith("Sitelink"))
                {
                    // prepare to skip sitelinks
                    currentState_ = ReaderState.SkippingSitelinks;
                    currentBrand_ = null;
                    currentCampaign_ = null;
                    currentAdGroup_ = null;
                    currentAd_ = null;
                }
                else if (Regex.Match(trimmedLine, "\\AFormat \\d").Success) /* if we see "Format 1", "Format 2", etc. */
                {
                    // ad has been fully assigned and the subsequent lines are format variations
                    currentState_ = ReaderState.SkippingFormatVariations;
                    currentAd_ = null;
                    return;
                }
            }

            // depending on what we are doing currently, interpret this line
            switch (currentState_)
            {
                case ReaderState.Unknown:
                    {
                        skipped_[line] = true; // if we don't know what to do, add this line to "skipped"
                        return;
                    }
                    break;
                case ReaderState.ReadingAdGroupDefinition:
                    {
                        string brandName;
                        if (TryGetTextValue(line, "Brand:", out brandName))
                        {
                            currentBrand_ = brands_.FindOrCreateBrand(brandName);
                            return;
                        }
                        string campaignType;
                        if (TryGetTextValue(line, "Campaign Type:", out campaignType))
                        {
                            if (null == currentBrand_)
                                throw new Exception("At line " + lineNumber + " Brand is not defined");
                            currentCampaign_ = currentBrand_.FindOrCreateCampaign(campaignType);
                            return;
                        }
                        string projectNumber;
                        if (TryGetTextValue(line, "Project Number:", out projectNumber))
                        {
                            if (null == currentCampaign_)
                                throw new Exception("At line " + lineNumber + " Campaign is not defined");
                            if (currentCampaign_.projectNumber != null && currentCampaign_.projectNumber != projectNumber)
                                throw new Exception("At line " + lineNumber + " project number for Campaign '" + currentCampaign_.campaignType + " switched from " + currentCampaign_.projectNumber + " to " + projectNumber);
                            currentCampaign_.projectNumber = projectNumber;
                        }
                        string adGroupName;
                        if (TryGetTextValue(line, "Ad & Keyword Group", out adGroupName))
                        {
                            if (null == currentCampaign_)
                                throw new Exception("At line " + lineNumber + " Campaign Type is not defined");
                            currentAdGroup_ = currentCampaign_.FindOrCreateAdGroup(adGroupName);
                            return; // line interpreted well
                        }
                        if (currentAdGroup_ != null /* if current ad group is defined */)
                        {
                            if (TryGetTextValue(line, lineNumber, "Mobile", ref currentAdGroup_.mobileUrl))
                                return; // line interpreted well
                            if (TryGetTextValue(line, lineNumber, "Computer Tablet", ref currentAdGroup_.tabletUrl))
                                return; // line interpreted well
                            if (TryGetTextValue(line, lineNumber, "Desktop/Laptop", ref currentAdGroup_.desktopUrl))
                                return; // line interpreted well
                        }
                    }
                    break;
                case ReaderState.ReadingAdDefinition:
                    {
                        if (null == currentAdGroup_)
                            throw new Exception("At line " + lineNumber + " Ad & Keyword Group is not defined");
                        string headline;
                        if (TryGetTextValue(line, "Headline", out headline))
                        {
                            // this means a start of a new ad (so create a new ad and add it into ad group)
                            currentAd_ = new Ad();
                            currentAdGroup_.ads.Add(currentAd_);
                            currentAd_.headline = headline;
                            return; // line interpreted well
                        }
                        if (null != currentAd_)
                        {
                            if (TryGetTextValue(line, lineNumber, "Line 1", ref currentAd_.line1))
                                return; // line interpreted well
                            if (TryGetTextValue(line, lineNumber, "Line 2", ref currentAd_.line2))
                                return; // line interpreted well
                            if (TryGetTextValue(line, lineNumber, "Type 1 URL", ref currentAd_.type1Url))
                                return; // line interpreted well
                            if (TryGetTextValue(line, lineNumber, "Type 2 URL", ref currentAd_.type2Url))
                                return; // line interpreted well
                            if (TryGetTextValue(line, lineNumber, "Shortened Type 1 URL", ref currentAd_.shortededType1Url))
                                return; // line interpreted well
                            if (TryGetTextValue(line, lineNumber, "Shortened Type 2 URL", ref currentAd_.shortededType2Url))
                                return; // line interpreted well
                        }
                    }
                    break;
                case ReaderState.SkippingFormatVariations:                    
                    return; // this means we intentionally skip these lines
                    break;
                case ReaderState.ReadingKeywords:
                    {
                        if (null == currentAdGroup_)
                            throw new Exception("At line " + lineNumber + " keywords for unknown Ad & Keyword Group");
                        string keyword;
                        if (TryGetTextValue(line, "\\d+" /* in REGEX language "\\d+" means "positive number" */, out keyword))
                        {
                            if (String.IsNullOrEmpty(keyword))
                            {
                                // this is probably just a page number
                                skipped_[line] = true;
                                return;
                            }
                            if (currentAdGroup_.keywords.Contains(keyword))
                            {
                                Console.WriteLine("WARNING: Ad Group '" + currentAdGroup_.adGroupName + "' already contains keyword '" + keyword + "' mentioned again at line " + lineNumber + ". Add a duplicate? [y/n]");
                                ConsoleKeyInfo key = Console.ReadKey(true);
                                if (!(key.KeyChar == 'n' || key.KeyChar == 'N'))
                                {
                                    Console.WriteLine("ARE YOU REALLY SURE? Ad Group '" + currentAdGroup_.adGroupName + "' ALREADY contains keyword '" + keyword + "' mentioned again at line " + lineNumber + ". Really add a duplicate? [y/n]");
                                    key = Console.ReadKey(true);
                                    if (!(key.KeyChar == 'n' || key.KeyChar == 'N'))
                                    {
                                        Console.WriteLine();
                                        Console.WriteLine("KEYWORD ADDED AS A DUPLICATE!");
                                        Console.WriteLine();
                                    }
                                    else return; // skipping a duplicate keyword
                                }
                                else return; // skipping a duplicate keyword
                            }
                            currentAdGroup_.keywords.Add(keyword);
                            return; // line interpreted well
                        }
                    }
                    break;
                case ReaderState.SkippingSitelinks:
                    return; // intentionally skipping sitelinks
                    break;
                default:
                    break;
            }

            // if we are here, we could not interpret this line, so remember it as skipped
            skipped_[line] = true;
            return;
        }

        // resutls
        public readonly Brands brands_ = new Brands();
        public readonly SortedDictionary<string, bool> skipped_ = new SortedDictionary<string, bool>();

        // state
        enum ReaderState
        {
            Unknown,
            ReadingAdGroupDefinition,
            ReadingAdDefinition,
            SkippingFormatVariations,
            ReadingKeywords,
            SkippingSitelinks,
        }
        ReaderState currentState_ = ReaderState.Unknown;
        Brand currentBrand_ = null;
        Campaign currentCampaign_ = null;
        AdGroup currentAdGroup_ = null;
        Ad currentAd_ = null;

        #region Utilities

        // utilities
        private static bool TryGetTextValue(string line, string key, out string value)
        {
            var contains = Regex.Match(line, "\\A\\s*" + key);
            if (contains.Success)
            {
                int location = line.IndexOf(contains.Value);
                if (location != -1)
                {
                    value = line.Substring(location + contains.Value.Length);
                    value = value.TrimStart(':', ' ', '', '\t', '\n', '\r').Replace('', ' ').Replace('\t', ' ');
                    int end = value.IndexOf("  ");
                    if (end != -1)
                        value = value.Substring(0, end);
                    return true;
                }
            }
            value = null;
            return false;
        }

        private static bool TryGetTextValue(string line, int lineNumber, string key, ref string value)
        {
            string v;
            if (!TryGetTextValue(line, key, out v))
                return false;
            if (value != null)
            {
                if (value != v)
                    throw new Exception("At line " + lineNumber + " the '" + key + " was already assigned");
                else return true; // already assigned correctly
            }
            value = v;
            return true;            
        }

        #endregion
    }
}
